﻿using System;
using System.Reflection;
using System.Threading;
using MessagingWPF.Infrastructure.Utilities;

namespace MessagingWPF.Infrastructure
{
	/// <summary>
	/// A thread safe subscription which holds all the information
	/// needed to send a message to the interested subscriber in a thread safe manner.
	/// </summary>
	public class InstanceSubscription : Subscription
	{
		private readonly MethodInfo _MethodHandle;
		private readonly SynchronizationContext _SyncContext;
		private readonly WeakReference _TargetReference;

		/// <summary>
		/// Initializes a new instance of the <see cref="Subscription"/> class.
		/// </summary>
		/// <param name="messageType">
		/// The message type this subscription is interested in.
		/// </param>
		/// <param name="subscriber">
		/// The subscriber instance.
		/// </param>
		/// <param name="threadContext">
		/// The thread context of the subscriber.
		/// </param>
		public InstanceSubscription(
			Type messageType,
			object subscriber,
			SynchronizationContext threadContext)
			: base(messageType)
		{
			Ensure.ArgumentIsNotNull(subscriber, "subscriber");
			Ensure.ArgumentIsNotNull(threadContext, "treadContext");

			_TargetReference = new WeakReference(subscriber);
			_SyncContext = threadContext;
			_MethodHandle = GetHandleMethod(subscriber.GetType());
		}

		/// <summary>
		/// Gets the instance to which this subscription belongs.
		/// </summary>
		public object Instance
		{
			get { return _TargetReference.Target; }
		}

		/// <summary>
		/// Gets a value indicating whether the subscription is alive.
		/// </summary>
		public bool IsAlive
		{
			get { return _TargetReference.IsAlive; }
		}

		/// <summary>
		/// Serves as a hash function for a particular type.
		/// </summary>
		/// <returns>A hash code for the current object.</returns>
		public override int GetHashCode()
		{
			var result = GetType().GetHashCode();
			result ^= _TargetReference.GetHashCode();
			result ^= MessageType.GetHashCode();
			result ^= _MethodHandle.GetHashCode();
			return result;
		}

		/// <summary>
		/// Indicates whether the subscription is equal to the specified subscription.
		/// </summary>
		/// <param name="subscription">A subscription to compare with this subscription.</param>
		/// <returns>
		/// <c>True</c> if the current object is equal to the <paramref name="subscription"/>
		/// parameter; otherwise, <c>false</c>.
		/// </returns>
		protected override bool EqualsCore(Subscription subscription)
		{
			var other = subscription as InstanceSubscription;

			return other != null &&
				other.MessageType == MessageType &&
					other._TargetReference.Target == _TargetReference.Target;
		}

		/// <summary>
		/// Sends the message to the subscribing instance. Performs 
		/// all thread marshalling so that the call is handled on
		/// the subscribers thread.
		/// </summary>
		/// <param name="message">The message to be send.</param>
		protected override void SendMessageCore(object message)
		{
			if (_TargetReference.IsAlive)
			{
				_SyncContext.Send(x => InvokeObserver(message), null);
			}
		}

		private void InvokeObserver(object message)
		{
			_MethodHandle.Invoke(_TargetReference.Target, new[] {message});
		}
	}
}